package algo.math.evaluator;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import algo.math.evaluator.base.IBase;
import algo.math.evaluator.base.exceptions.InvalidToken;
import algo.math.evaluator.base.exceptions.UnrecognizedToken;
import algo.math.evaluator.context.EvaluationContext;
import algo.math.evaluator.context.IComponentObject;
import algo.math.evaluator.context.functions.interfaces.IFunction;
import algo.math.evaluator.context.operator.Operator;
import algo.math.evaluator.context.operator.interfaces.IOperator;
import algo.math.evaluator.exceptions.InvalidOperationException;
import algo.math.evaluator.exceptions.SyntaxException;
import algo.math.evaluator.exceptions.UnrecognizedFunctionException;
import algo.math.evaluator.exceptions.UnrecognizedVariableException;
import algo.math.evaluator.expression.Expression;
import algo.math.evaluator.stack.LinkedStackSimple;
import algo.math.evaluator.stack.interfaces.ILifo;
import algo.math.evaluator.tokenizer.Token;
import algo.math.evaluator.tokenizer.interfaces.IToken;
import algo.math.evaluator.tokenizer.interfaces.IToken.TokenType;
import algo.math.evaluator.validator.Validator;
import algo.math.evaluator.value.IValue;
import algo.math.evaluator.value.Value;

// TODO: Auto-generated Javadoc
/**
 * Represent an arithmetic evaluator.
 */
public class Evaluator {

		
	
    /**
     * The main evaluation method. Process the product of the tokenizer.
     *
     * @param A infix Expression
     * @return IValue The product of the evaluation 
     * @throws Exception Any exception thrown by the tokenizer or the evaluations functions
     */
    public IValue evaluate(Expression expression) throws Exception
    {
        ILifo<IToken> stack = new LinkedStackSimple<IToken>();
        List<IToken> postfixTokens = new Validator().infixToPostfix(expression).getTokens();
          
        
        for (IToken token : postfixTokens)
        {
            if (token.getType() == TokenType.CONSTANT)
            {
                stack.push(token);
            }
            
            else if (token.getType() == TokenType.FUNCTION)
            {
            	stack.push(evaluateFunction(token));
            }
            
            else if (token.getType() == TokenType.VARIABLE)
            {
            	stack.push(token);
            }
            
            else if (token.getType() == TokenType.OPERATOR)
            {
	               List<IToken> l = new ArrayList<IToken>();
	            	
	               for (int i =0; i< ((IOperator)token).getNbOperands();i++)
	               {
	            	   if (stack.isEmpty())
	            	   {
	            		   l.add( new Token (String.valueOf("0"),TokenType.CONSTANT));
	            	   }
	            	   else
	            		   l.add(stack.pop());
	               }
	               	Token returnValue;
	            	Operator op = (Operator) token;
	            	returnValue = evaluateArithmetic(l, op,expression.getVariables(),expression.getBase());
	            	stack.push(returnValue); 
            }
        }

        IValue result = null;
        
        if (stack.size()== 1)
        {
            IToken top = null;
			top = stack.pop();
			result = new Value();
			result.setValue(top.getValue(), EvaluationContext.GetInstance().getBase());
        }
        else
        {
        	throw new SyntaxException("Invalid Expression");
        }
        
        return (result);
    }
	
    
    /**
     * Evaluate a function.
     *
     * @param IToken representing a function
     * @return Token The product of the function
     * @throws UnrecognizedFunctionException
     */
    private Token evaluateFunction (IToken token1) throws UnrecognizedFunctionException
    {
      try
    	{
    	IFunction fct = null;
    	
    	for (IComponentObject f : EvaluationContext.GetInstance().getComponents(IFunction.class,EvaluationContext.TYPE_COMPONENT.REGISTERED)) {
    		
    		if (((IFunction)f).getName().length()<= token1.getValue().length())
    		{
    			if (((IFunction)f).getName().equals(token1.getValue().substring(0, ((IFunction)f).getName().length())))
    			{
    				fct =((IFunction)f);
    				break;
    			}
    		}
    	}

		String val ="";
		IValue [] args = new IValue [fct.getArgsNumber()];
		int i = 0;
		int index =0;
		
		while (i< token1.getValue().length())
		{
			if (Character.isDigit(token1.getValue().charAt(i)))
			{
				while (Character.isDigit(token1.getValue().charAt(i)) || token1.getValue().charAt(i)=='.') 
				{
					val+=token1.getValue().charAt(i);
					i++;
				}
					args[index] = new Value();
					args[index].setValue(val, EvaluationContext.GetInstance().getBase());
					val="";
					index++;
			}
					i++;
		}
		
    	return (new Token(String.valueOf(fct.resolve(args)),TokenType.CONSTANT));
    	}
    	
    	catch (Exception e)
    	{
    		throw new UnrecognizedFunctionException ("Unrecognized function or invalid argument in function "+ token1.getValue() );
    		
    	}
    }
    
    
    /**
     * Evaluate a variable.
     *
     * @param IToken representing a variable
     * @param The context variables
     * @return Token The product of the function
     * @throws UnrecognizedVariableException the unrecognized variable exception
     */
    private Token evaluateVariable (IToken token1,Map<String, Object> variables) throws UnrecognizedVariableException
    {
    	for (String key : variables.keySet()) {
			if (key.equals(token1.getValue()))
			{
				return (new Token(String.valueOf(variables.get(key)),TokenType.CONSTANT));
			}
		}
    	
    	return (new Token("0",TokenType.CONSTANT));
    }
    
    /**
     * Evaluate a constant. 
     *
     * @param IToken representing a constant
     * @param The context base
     * @return IToken Return the constant value
     */
    private IToken evaluateConstant (IToken token1,IBase base) 
    {
    	IToken t;
		t = new Token (String.valueOf(token1.getValue()),TokenType.CONSTANT);
		return t;
    }
    
    /**
     * Handle basic evaluation (Token|Operator|Token)
     *
     * @param Operands list
     * @param Operator
     * @param The context variables
     * @param The context number base
     * @return Token. The evaluation product
     * @throws UnrecognizedFunctionException 
     * @throws UnrecognizedVariableException 
     * @throws UnrecognizedToken 
     * @throws InvalidOperationException 
     * @throws InvalidToken 
     */
    private Token evaluateArithmetic (List<IToken> operands, Operator op,Map<String, Object> variables, IBase base) throws UnrecognizedFunctionException, UnrecognizedVariableException, UnrecognizedToken, InvalidToken, InvalidOperationException 
    {
    	
    	String []values = new String [operands.size()];
        int i =0;
        
    	for (IToken iToken : operands) {
			
    		 if (iToken.getType()==TokenType.FUNCTION)
             {
    			 iToken = evaluateFunction(iToken);
    			 values[i]=iToken.getValue();
             }
    		 
    		 else if (iToken.getType()==TokenType.VARIABLE)
    		 {
    			 iToken = evaluateVariable(iToken,variables);
    			 values[i]=iToken.getValue();
    		 }
    		 
    		 else if (iToken.getType()==TokenType.CONSTANT)
    		 {
    			 iToken = evaluateConstant(iToken,base);
    			 values[i]=iToken.getValue();
    		 }
		
    		 i++;
    	}
    	IValue[]opes = new IValue[values.length];

    	int z=values.length-1;
    	
    	for (String string : values) {
    		IValue val = new Value();
    		val.setValue(string, EvaluationContext.GetInstance().getBase());
    		opes[z] = val;
    		z--;
		}

        IValue result = op.evaluate(opes);
        
        Token r = new Token (result.toString(),TokenType.CONSTANT);
        return r; 
    	
    }
}

